/**
 * Copyright (c) 2018-2022, NXOS Development Team
 * SPDX-License-Identifier: Apache-2.0
 * 
 * Contains: keyboard driver
 * 
 * Change Logs:
 * Date           Author            Notes
 * 2022-06-03     JasonHu           Init
 */

#define NX_LOG_NAME "ps2keyboard"
#define NX_LOG_LEVEL NX_LOG_INFO
#include <base/log.h>

#ifdef CONFIG_NX_DRIVER_PS2_KEYBOARD

#include <base/page.h>
#include <base/driver.h>
#include <base/malloc.h>
#include <base/debug.h>
#include <base/delay_irq.h>
#include <base/fifo.h>
#include <base/semaphore.h>
#include <base/thread.h>
#include <base/memory.h>

#include <drvfw/input_event.h>

#include <interrupt.h>
#include <io.h>

#include "ps2_keyboard.h"

#define DRV_NAME "ps2 keyboard driver"
#define DRV_VERSION "0.1"

#define DEV_NAME "keyboard0"

#define KBD_EVENT_QUEUE_MAX_SZ  32
#define KBD_FIFO_MAX_SZ  32

typedef struct HalDeviceExtension
{
    char irq;           /* irq号 */
    
    int	code_with_e0;	/* 携带E0的值 */
	int	shift_left;	/* l shift state */
	int	shift_right;	/* r shift state */
	int	alt_left;	/* l alt state	 */
	int	alt_right;	/* r left state	 */
	int	ctl_left;	/* l ctrl state	 */
	int	ctl_right;	/* l ctrl state	 */
	int	caps_lock;	/* Caps Lock	 */
	int	num_lock;	/* Num Lock	 */
	int	scroll_lock;	/* Scroll Lock	 */
	int	column;		/* 数据位于哪一列 */

    NX_Fifo * fifo;
    NX_Semaphore fifoSem;
    NX_InputEventQueue eventQueue;

    NX_U32 flags;
} HalDeviceExtension;

NX_PRIVATE HalDeviceExtension halKeyboardExtension;

NX_PRIVATE NX_U16 ScanCode2EventCode(int key)
{
    int i;
    for (i = 0; i < NX_ARRAY_SIZE(map_table); i++) {
        if (i % 2 == 0) {   /* 偶数：源键值码 */
            if (map_table[i] == key) {
                return map_table[i + 1];
            }
        }
    };
    return NX_KEY_UNKNOWN; /* 未知编码 */
}

NX_PRIVATE void WAIT_KBC_ACK(void)
{
	unsigned char read;
	do {
		read = IO_In8(KBC_READ_DATA);
	} while ((read != KBC_RET_ACK));
}

NX_PRIVATE void SetLeds(HalDeviceExtension *ext)
{
	/* 先合成成为一个数据，后面写入寄存器 */
	unsigned char leds = (ext->caps_lock << 2) | 
        (ext->num_lock << 1) | ext->scroll_lock;
	
	/* 数据指向led */
	WAIT_KBC_WRITE();
	IO_Out8(KBC_WRITE_DATA, KEY_CMD_LED_CODE);
	WAIT_KBC_ACK();
	/* 写入新的led值 */
	WAIT_KBC_WRITE();
	IO_Out8(KBC_WRITE_DATA, leds);
    WAIT_KBC_ACK();
}

NX_PRIVATE NX_U8 GetScanCodeFromFifo(HalDeviceExtension *ext)       
{
    NX_U8 scan_code = 0;
    NX_SemaphoreWait(&ext->fifoSem);
    NX_ASSERT(NX_FifoRead(ext->fifo, &scan_code, 1) > 0);
    return scan_code;
}

NX_PRIVATE NX_U32 ParseKeyboard(HalDeviceExtension * ext)
{
	NX_U8 scan_code;
	int make;
	
	NX_U32 key = 0;
	NX_U32 *keyrow;

	ext->code_with_e0 = 0;

	scan_code = GetScanCodeFromFifo(ext);
	
	/* 检查是否是0xe1打头的数据 */
	if(scan_code == 0xe1){
		int i;
		NX_U8 pausebrk_scode[] = {0xE1, 0x1D, 0x45, 0xE1, 0x9D, 0xC5};
		int is_pausebreak = 1;
		for(i = 1; i < 6; i++){
			if (GetScanCodeFromFifo(ext) != pausebrk_scode[i]) {
				is_pausebreak = 0;
				break;
			}
		}
		if (is_pausebreak) {
			key = KBD_PAUSEBREAK;
		}
	} else if(scan_code == 0xe0){
		/* 检查是否是0xe0打头的数据 */
		scan_code = GetScanCodeFromFifo(ext);

		//PrintScreen 被按下
		if (scan_code == 0x2A) {
			if (GetScanCodeFromFifo(ext) == 0xE0) {
				if (GetScanCodeFromFifo(ext) == 0x37) {
					key = KBD_PRINTSCREEN;
					make = 1;
				}
			}
		}
		//PrintScreen 被释放
		if (scan_code == 0xB7) {
			if (GetScanCodeFromFifo(ext) == 0xE0) {
				if (GetScanCodeFromFifo(ext) == 0xAA) {
					key = KBD_PRINTSCREEN;
					make = 0;
				}
			}
		}
		//不是PrintScreen, 此时scan_code为0xE0紧跟的那个值. 
		if (key == 0) {
			ext->code_with_e0 = 1;
		}
	}
    
    if ((key != KBD_PAUSEBREAK) && (key != KBD_PRINTSCREEN)) {
		/* 处理一般字符 */
		make = (scan_code & KBD_FLAG_BREAK_MASK ? 0 : 1);

		//先定位到 kbd_keymap 中的行 
		keyrow = &kbd_keymap[(scan_code & 0x7F) * KEYMAP_COLS];
		
		ext->column = 0;
		int caps = ext->shift_left || ext->shift_right;
		if (ext->caps_lock) {
			if ((keyrow[0] >= 'a') && (keyrow[0] <= 'z')){
				caps = !caps;
			}
		}
        /* 如果大写打开 */
		if (caps) {
			ext->column = 1;
		}

        /* 如果有0xE0数据 */
		if (ext->code_with_e0) {
			ext->column = 2;
		}
		/* 读取列中的数据 */
		key = keyrow[ext->column];
		
        /* shift，ctl，alt变量设置，
        caps，num，scroll锁设置 */
		switch(key) {
		case KBD_SHIFT_L:
			ext->shift_left = make;
			break;
		case KBD_SHIFT_R:
			ext->shift_right = make;
			break;
		case KBD_CTRL_L:
			ext->ctl_left = make;
			break;
		case KBD_CTRL_R:
			ext->ctl_right = make;
			break;
		case KBD_ALT_L:
			ext->alt_left = make;
			break;
		case KBD_ALT_R:
			ext->alt_left = make;
			break;
		case KBD_CAPS_LOCK:
			if (make) {
				ext->caps_lock   = !ext->caps_lock;
				SetLeds(ext);
			}
			break;
		case KBD_NUM_LOCK:
			if (make) {
             	ext->num_lock    = !ext->num_lock;
				SetLeds(ext);
			}
			break;
		case KBD_SCROLL_LOCK:
			if (make) {
             	ext->scroll_lock = !ext->scroll_lock;
				SetLeds(ext);
			}
			break;	
		default:
			break;
		}
        int pad = 0;
        //首先处理小键盘
        if ((key >= KBD_PAD_SLASH) && (key <= KBD_PAD_9)) {
            pad = 1;
        }
        /* 如果有组合件，就需要合成成为组合后的按钮，可以是ctl+alt+shift+按键的格式 */
        key |= ext->shift_left	? KBD_FLAG_SHIFT_L	: 0;
        key |= ext->shift_right	? KBD_FLAG_SHIFT_R	: 0;
        key |= ext->ctl_left	? KBD_FLAG_CTRL_L	: 0;
        key |= ext->ctl_right	? KBD_FLAG_CTRL_R	: 0;
        key |= ext->alt_left	? KBD_FLAG_ALT_L	: 0;
        key |= ext->alt_right	? KBD_FLAG_ALT_R	: 0;
        key |= pad      ? KBD_FLAG_PAD      : 0;

        /* 如果是BREAK,就需要添加BREAK标志 */
        key |= make ? 0: KBD_FLAG_BREAK;
        
        /* 设置锁标志 */
        key |= ext->num_lock ? KBD_FLAG_NUM : 0;
        key |= ext->caps_lock ? KBD_FLAG_CAPS : 0;
        
        /* 把按键输出 */
        return key;
	}
    return KEYCODE_NONE;
}

NX_PRIVATE NX_Error KeyboardRead(struct NX_Device *device, void *buf, NX_Offset off, NX_Size len, NX_Size *outLen)
{
    HalDeviceExtension * ext = device->extension;

    NX_InputEvent e;

    if (!len || len != sizeof(NX_InputEvent))
    {
        return NX_EINVAL;
    }

    if (NX_InputEventQueueGet(&ext->eventQueue, &e) != NX_EOK)
    {
        return NX_EAGAIN;
    }

    NX_MemCopy(buf, &e, sizeof(NX_InputEvent));

    if (outLen)
    {
        *outLen = len;
    }
    return NX_EOK;
}

NX_PRIVATE NX_Error KeyboardControl(struct NX_Device *device, NX_U32 cmd, void *arg)
{
    HalDeviceExtension * ext = device->extension;
    NX_U32 leds;

    NX_ASSERT(ext);

    switch (cmd)
    {
    case NX_INPUT_EVENT_CMD_GETLED:
        leds =  ext->num_lock | (ext->caps_lock << 1) | (ext->scroll_lock << 2);
        NX_MemCopy(arg, &leds, sizeof(leds));
        break;
    default:
        return NX_EINVAL;
    }

    return NX_EOK;
}

NX_PRIVATE NX_Error KeyboardPoll(struct NX_Device *device, NX_PollState * pState)
{
    HalDeviceExtension * ext = device->extension;

    if (NX_InputEventQueueEmpty(&ext->eventQueue))
    {
        return NX_ENORES;
    }

    pState->outmask = NX_POLL_READ;

    return NX_EOK;
}

NX_PRIVATE NX_DriverOps KeyboardDriverOps = {
    .read       = KeyboardRead,
    .control    = KeyboardControl,
    .poll       = KeyboardPoll,
};

NX_PRIVATE NX_Error KeyboardIrqHandler(NX_IRQ_Number irqno, void *arg)
{
    HalDeviceExtension * kbd = (HalDeviceExtension *)arg;

    NX_U8 scan_code = IO_In8(KBC_READ_DATA);
    /* save scan code to fifo buffer */
    NX_FifoWrite(kbd->fifo, &scan_code, 1);
    NX_SemaphoreSignal(&kbd->fifoSem);
    return NX_EOK;
}

NX_PRIVATE void KeyboardWorkerThread(void *arg)
{
    HalDeviceExtension *ext = (HalDeviceExtension *) arg;
    NX_U32 key;
    while (1) {
        key = 0;
        key = ParseKeyboard(ext);
        if (key > 0 && (key & KBD_KEY_MASK))
        {
            NX_InputEvent e;
            e.type = NX_EV_KEY;
            if (key & KBD_FLAG_BREAK) {
                e.value = 0;
            } else {
                e.value = 1;
            }
            e.code = ScanCode2EventCode(key & KBD_KEY_MASK);

            NX_InputEventQueuePut(&ext->eventQueue, &e);

            NX_LOG_D("key even set: type=%d code=%x value=%d", e.type, e.code, e.value);
            NX_LOG_D("key even buf: head=%d tail=%d", ext->eventQueue.head, ext->eventQueue.tail);
            NX_LOG_D("kbd_thread: key:%c", key);
        }
    }
}

NX_PRIVATE void KeyboardDriverInit(void)
{
    NX_Device *device;
    HalDeviceExtension * kbd;

    NX_Driver *driver = NX_DriverCreate(DRV_NAME, NX_DEVICE_TYPE_INPUT, 0, &KeyboardDriverOps);
    if (driver == NX_NULL)
    {
        NX_LOG_E("create driver failed!");
        return;
    }

    if (NX_DriverAttachDevice(driver, DEV_NAME, &device) != NX_EOK)
    {
        NX_LOG_E("attach device %s failed!", DEV_NAME);
        NX_DriverDestroy(driver);
        return;
    }

    kbd = &halKeyboardExtension;
    device->extension = kbd;

    kbd->irq = IRQ_KEYBOARD;
    kbd->flags = 0;
    
	/* 初始化私有数据 */
	kbd->code_with_e0 = 0;
	
	kbd->shift_left	= kbd->shift_right = 0;
	kbd->alt_left	= kbd->alt_right   = 0;
	kbd->ctl_left	= kbd->ctl_right  = 0;
	
	kbd->caps_lock   = 0;
	kbd->num_lock    = 1;
	kbd->scroll_lock = 0;

    kbd->fifo = NX_FifoCreate(KBD_FIFO_MAX_SZ);
    if (kbd->fifo == NX_NULL)
    {
        NX_LOG_E("create %s fifo failed!", DEV_NAME);
        
        NX_DriverDetachDevice(driver, DEV_NAME);
        NX_DriverDestroy(driver);
        return;
    }
    NX_SemaphoreInit(&kbd->fifoSem, 0);
    NX_InputEventQueueInit(&kbd->eventQueue, KBD_EVENT_QUEUE_MAX_SZ);

    /* 发送写配置命令 */
    WAIT_KBC_WRITE();
	IO_Out8(KBC_CMD, KBC_CMD_WRITE_CONFIG);

    /* 往数据端口写入配置值 */
    WAIT_KBC_WRITE();
	IO_Out8(KBC_WRITE_DATA, KBC_CONFIG);

    NX_ASSERT(NX_IRQ_Bind(kbd->irq, KeyboardIrqHandler, kbd, "Keyboard", 0) == NX_EOK);
    NX_ASSERT(NX_IRQ_Unmask(kbd->irq) == NX_EOK);

    if (NX_DriverRegister(driver) != NX_EOK)
    {
        NX_LOG_E("register driver %s failed!", DRV_NAME);

        NX_FifoDestroy(kbd->fifo);

        NX_ASSERT(NX_IRQ_Mask(kbd->irq) == NX_EOK);
        NX_ASSERT(NX_IRQ_Unbind(kbd->irq, kbd) == NX_EOK);
        NX_InputEventQueueExit(&kbd->eventQueue);

        NX_DriverDetachDevice(driver, DEV_NAME);
        NX_DriverDestroy(driver);
        return;
    }
    NX_ASSERT(NX_ThreadStart(NX_ThreadCreate("kbd_worker", KeyboardWorkerThread, kbd, NX_THREAD_PRIORITY_MAX)) == NX_EOK);
}

NX_PRIVATE void KeyboardDriverExit(void)
{
    NX_DriverCleanup(DRV_NAME);
}

NX_DRV_INIT(KeyboardDriverInit);
NX_DRV_EXIT(KeyboardDriverExit);

#endif /* CONFIG_NX_DRIVER_PS2_KEYBOARD */
